package cn.getech.data.development.service.impl;

import cn.getech.data.development.constant.PermissionTypeEnum;
import cn.getech.data.development.entity.EsSearch;
import cn.getech.data.development.entity.EsSearchGroup;
import cn.getech.data.development.mapper.GlobalSearchMapper;
import cn.getech.data.development.service.GlobalSearchService;
import cn.getech.data.development.utils.DataPermissionUtil;
import cn.getech.system.center.entity.SysMenuEntity;
import cn.getech.system.center.mapper.SysMenuMapper;
import cn.getech.system.center.service.SysMenuService;
import cn.getech.system.center.utils.ShiroUtils;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author ：zhibin3.liang
 * @version : 1.0
 * @date ：Created in 2021/9/27 16:16
 * @description：全局搜索
 */
@Slf4j
@Service
public class GlobalSearchServiceImpl implements GlobalSearchService {

    @Resource
    private RestHighLevelClient client;

    @Autowired
    private GlobalSearchMapper globalSearchMapper;

//    @Autowired
//    private SysMenuMapper sysMenuMapper;

    @Resource
    private SysMenuService sysMenuService;

    @Override
    public JSONObject getHit(Map<String,Object> params) {
        JSONObject finalRes = new JSONObject();
        Long userId = ShiroUtils.getUserId();
        Integer tenantId = ShiroUtils.getTenantId();
        int pageNumber = 1;
        int pageSize = 8;

        if(params.containsKey("searchContent")){
            String content = params.get("searchContent").toString();

            if(params.containsKey("pageNumber") && params.containsKey("pageSize")){
                pageNumber = (int) params.get("pageNumber");
                pageSize = (int) params.get("pageSize");
            }
            // 存匹配所有索引的结果集合
            List<EsSearch> allSearchRes = new ArrayList();
            JSONObject totalResult = new JSONObject();
            JSONObject dataDevResult = new JSONObject();

            //查项目
            HashMap<String , String> cond = new HashMap<>();
            cond.put("tenant_id",  tenantId.toString());
            cond.put("user_id", userId.toString());
            SearchHit[] response = singleCommonSearch("bdp_proc_info", content, 0, 100, "name", cond, "name");
            allSearchRes.addAll(dealSingleResult( response, "name" ,"name"));

            // 查数据库表
            HashMap<String , Object> cond1 = new HashMap<>();
            List<Integer> ids = DataPermissionUtil.getDataIds(userId.intValue(), PermissionTypeEnum.STORE.getType(), PermissionTypeEnum.QUERY.getType());
            if (CollectionUtils.isEmpty(ids)) {
                ids.add(-1);//无权限查询-1的数据
            }
            cond1.put("ids",  ids);
//            SearchHit[] response1 = singleCommonSearch("bdp_proc_info", content, 0, 100, "name", cond, "name");

            // 根据匹配度对所有结果进行排序
            List<EsSearch> sortedRes = allSearchRes.stream().sorted(Comparator.comparingDouble(EsSearch::getScore)).collect(Collectors.toList());

            //全部搜索结果
            List<EsSearch> allGroupsPage = sortedRes.stream().skip((pageNumber - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
            totalResult.put("total", sortedRes.size());
            totalResult.put("result", allGroupsPage);

            // 根据一级菜单 数据开发平台 进行分组
            int size = sortedRes.stream().filter(p -> p.getParentId().equals(3)).collect(Collectors.toList()).size();
            List<EsSearch> dataDev = sortedRes.stream().filter(p -> p.getParentId().equals(3)).skip((pageNumber - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
            dataDevResult.put("total", size);
            dataDevResult.put("dataDev", dataDev);
//            Map<Integer, List<EsSearch>> dataDev = sortedRes.stream().filter(p -> p.getParentId().equals(3)).skip((pageNumber-1+1)* pageSize).limit(pageSize).collect(Collectors.groupingBy(EsSearch::getParentId));
//            Iterator<Map.Entry<Integer, List<EsSearch>>> iterator = dataDev.entrySet().iterator();
//            while (iterator.hasNext()){
//                iterator.next().getKey();
//            }
            // 筛选匹配度最高的5条
            List<EsSearch> sortedResTop5 = sortedRes.stream().limit(5).collect(Collectors.toList());
            finalRes.put("top5", sortedResTop5);
            finalRes.put("totalResult", totalResult);
            finalRes.put("dataDevResult", dataDevResult);

        }
        return finalRes;
    }

    /**
     * @author     : zhibin3.liang
     * @date       : Created in 2021/10/8 15:15
     * @description: 根据排序集合 返回一级目录下 各分组的层级结果
     * @param sortedRes 按评分排序的集合
     * @param params 前端传参
     */
    public JSONObject splitSearchRes(List<EsSearch> sortedRes, Map<String,Object> params){

        // 用于存一级目录分组的结果
        JSONObject parentRes = new JSONObject();
        Map<Integer, List<EsSearch>> collectGroup = sortedRes.stream().collect(Collectors.groupingBy(EsSearch::getParentId));
        Iterator<Map.Entry<Integer, List<EsSearch>>> parentSets = collectGroup.entrySet().iterator();
        while (parentSets.hasNext()){
            // 遍历每个一级菜单 按组id进行分组
            Map.Entry<Integer, List<EsSearch>> parent = parentSets.next();
            Map<Long, List<EsSearch>> allGroupRes;
            if(params.containsKey("typeId")){
                Long typeId = (Long) params.get("typeId");
                allGroupRes = parent.getValue().stream().filter(p -> p.getGroupId().equals(typeId)).collect(Collectors.groupingBy(EsSearch::getGroupId));
            }else {
                allGroupRes = parent.getValue().stream().collect(Collectors.groupingBy(EsSearch::getGroupId));
            }

            JSONObject childRes = new JSONObject();
            ArrayList<JSONObject> childResList = new ArrayList<>();

            Iterator<Map.Entry<Long, List<EsSearch>>> groupSets = allGroupRes.entrySet().iterator();
            while (groupSets.hasNext()){
                // 遍历某一级菜单 某组id 下的搜索结果
                Map.Entry<Long, List<EsSearch>> res = groupSets.next();
                childRes.put("groupRes", res.getValue());
                childRes.put("groupId", res.getKey());
                // 项目组下的结果 分组名字一样，取第一个的即可
                if(res.getValue().size() > 0){
                    childRes.put("groupName", res.getValue().get(0).getGroupName());
                }else {
                    childRes.put("groupName", "");
                }
                childRes.put("total", res.getValue().size());
            }
            childResList.add(childRes);
            parentRes.put("parentId", parent.getKey());
            parentRes.put("total", parent.getValue().size());
            parentRes.put("parentRes", childResList);
        }
        return parentRes;
    }

    /**
     * @description: 获取全局搜索下的分组类别
     */
    @Override
    public List<EsSearchGroup> getEsSearchGroup() {

        List<EsSearchGroup> esSearchGroup = globalSearchMapper.getEsSearchGroup();
        return esSearchGroup;
    }

    /**
     * 关键字搜索测试
     * @param index
     * @param value
     * @return
     */
    public String keywordSearch(String index, String value) {
        List<Map<String, Object>> result = null;
        try{
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices(index);
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(QueryBuilders
                    .multiMatchQuery(value, "username","name","desc")
                    .analyzer("ik_smart")
                    .fuzziness(1));

            searchSourceBuilder.postFilter(QueryBuilders.termQuery("tenant_id", "16"));
            searchRequest.source(searchSourceBuilder);
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(result);
            System.out.println(searchResponse.getHits().getTotalHits());

        }catch(IOException e){
            e.printStackTrace();
        }
        return JSONObject.toJSONString(result);
    }
    /**
     * @author     : zhibin3.liang
     * @date       : Created in 2021/9/29 16:39
     * @description: 按关键字 单个字段 单个高亮，多条件的全局搜索
     * @param index 索引名称
     * @param content 搜索内容
     * @param startIndex 返回结果第几条开始
     * @param maxSearchSize 最大返回条数
     * @param searchField 搜索字段
     * @param condition 搜索条件
     * @param highlightField 需要高亮度字段
     * @return : 返回命中的结果数组
     */
    public SearchHit[] singleCommonSearch(String index, String content, int startIndex, int maxSearchSize, String searchField , HashMap<String,String> condition, String highlightField) {

        SearchHit[] searchHitArr = null;
        try{
            SearchRequest searchRequest = new SearchRequest();
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            // 置入索引
            searchRequest.indices(index);
            // 配置查询内容 和 查询字段
            searchSourceBuilder.query(QueryBuilders
                    .multiMatchQuery(content, searchField)
                    .analyzer("ik_smart")
                    .fuzziness(1));
            // 配置结果条数范围
            searchSourceBuilder.from(startIndex);
            searchSourceBuilder.size(maxSearchSize);
            // 配置需过滤的条件
            Iterator<Map.Entry<String,String>> cond = condition.entrySet().iterator();
            while (cond.hasNext()){
                Map.Entry<String, String> next = cond.next();
                searchSourceBuilder.postFilter(QueryBuilders.termQuery(next.getKey(), next.getValue()));
            }
            // 设置需高亮的字段
            highlightBuilder.field(highlightField).requireFieldMatch(true).preTags("<span style='color:#1740dc'>").postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
            // 构建并返回响应
            searchRequest.source(searchSourceBuilder);
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            // 返回命中条件结果集
            searchHitArr = searchResponse.getHits().getHits();
            log.info("TotalHitsNum " + searchResponse.getHits().getTotalHits().toString());

        }catch(IOException e){
            e.printStackTrace();
        }
        return searchHitArr;
    }

    /**
     * @author     : zhibin3.liang
     * @date       : Created in 2021/9/29 16:39
     * @description: 按关键字多个 字段 条件 高亮 的全局搜索
     * @param index 索引名称
     * @param content 搜索内容
     * @param startIndex 返回结果第几条开始
     * @param maxSearchSize 最大返回条数
     * @param searchFields 搜索字段
     * @param condition 搜索条件
     * @param highlightFields 需要高亮度字段
     * @return : 返回命中的结果数组
     */
    public SearchHit[] mutilCommonSearch(String index, String content, int startIndex, int maxSearchSize, ArrayList<String> searchFields , HashMap<String,String> condition,ArrayList<String> highlightFields) {

        SearchHit[] searchHitArr = null;
        try{
            SearchRequest searchRequest = new SearchRequest();
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            String[] seaStr = new String[searchFields.size()];
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            // 置入索引
            searchRequest.indices(index);
            // 配置查询内容 和 查询字段
            searchSourceBuilder.query(QueryBuilders
                    .multiMatchQuery(content,searchFields.toArray(seaStr))
                    .analyzer("ik_smart")
                    .fuzziness(1));
            // 配置结果条数范围
            searchSourceBuilder.from(startIndex);
            searchSourceBuilder.size(maxSearchSize);
            // 配置需过滤的条件
            Iterator<Map.Entry<String,String>> cond = condition.entrySet().iterator();
            while (cond.hasNext()){
                Map.Entry<String, String> next = cond.next();
                searchSourceBuilder.postFilter(QueryBuilders.termQuery(next.getKey(), next.getValue()));
            }
            // 设置需高亮的字段
            if(!highlightFields.isEmpty()){
                for(String higStr : highlightFields){
                    highlightBuilder.field(higStr).requireFieldMatch(false).preTags("<span style='color:red'>").postTags("</span>");
                    searchSourceBuilder.highlighter(highlightBuilder);
                }
            }
            // 构建并返回响应
            searchRequest.source(searchSourceBuilder);
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            // 返回命中条件结果集
            searchHitArr = searchResponse.getHits().getHits();
            log.info("TotalHitsNum " + searchResponse.getHits().getTotalHits().toString());

        }catch(IOException e){
            e.printStackTrace();
        }
        return searchHitArr;
    }
    /**
     * @author     : zhibin3.liang
     * @date       : Created in 2021/9/29 17:07
     * @description: 处理es返回结果并封装成List<EsSearch>
     * @param searchHitArr es返回结果
     * @param searchField 搜索字段
     * @param highlightField 高亮字段
     * @return : java.util.List<cn.getech.data.development.entity.EsSearch>
     */
    public List<EsSearch> dealSingleResult( SearchHit[] searchHitArr, String searchField, String highlightField){

        List<EsSearch> result = new ArrayList<>();
        JSONObject jsonObject = new JSONObject();

        // 遍历es返回结果
        for (SearchHit searchHit : searchHitArr){
            // 获取返回结果中的所有key
            Map<String, Object> source = searchHit.getSourceAsMap();
            // 索引名称为数据库表名，用于查询跳转路径等参数
            String tableName = searchHit.getIndex();
            List<EsSearch> configs = globalSearchMapper.getConfigByTableName(tableName);
            // 遍历配置表中所需跳转到路由
            for(EsSearch con : configs){
                // 835 为 项目管理路由 只有本人建的项目才能封装进EsSearch结果中
                if("835".equals(con.getMenuId()) && source.containsKey("create_per") && source.get("create_per") != ShiroUtils.getUserId()){
                    continue;
                }
                // 匹配全词
                con.setMatchWord(source.get(searchField).toString());
                // 匹配分数
                con.setScore(searchHit.getScore());
                // 拿到菜单来源路径
                con.setMenuSource(getMenuSource(con.getMenuId()));
                // 获取跳转路径所需key 在 es结果中对应的key
                String[] originParamKeys = con.getOriginParam().split(";");
                // 获取跳转路径所需参数key
                String[] urlParamKeys = con.getUrlParam().split(";");
                // 封装跳转参数
                HashMap<String, Object> paramMap = new HashMap<>();
                if(originParamKeys.length == urlParamKeys.length){
                    for(int i=0; i<originParamKeys.length; i++){
                        paramMap.put(urlParamKeys[i] , source.get(originParamKeys[i]));
                    }
                    con.setUrlParamMap(paramMap);
                }
                //获取高亮字段
                Map<String, HighlightField> hitHighlightFields = searchHit.getHighlightFields();
                String hitFields = hitHighlightFields.get(highlightField).getFragments()[0].toString();
                con.setHighlightWord(hitFields);

                result.add(con);
            }
        }
        return result;
    }

    /**
     * @author     : zhibin3.liang
     * @date       : Created in 2021/9/29 16:33
     * @description: 根据菜单id 获取所有父级的名字
     * @param menuId 当前租户下的用户所拥有菜单id
     */
    public ArrayList<String> getMenuSource(Long menuId){

        ArrayList<String> result = new ArrayList<>();
        Integer tenantId = ShiroUtils.getTenantId();
        Long userId = ShiroUtils.getUserId();
        // 根据用户id 和 租户id 查询拥有的所有菜单
        List<SysMenuEntity> userMenus = sysMenuService.getUserMenuListById(userId, tenantId);
        // 转成map 方便拿Name 和 ParentId
        Map<Long, SysMenuEntity> userMenusMap = userMenus.stream().collect(Collectors.toMap(SysMenuEntity::getMenuId, sysMenuEntity -> sysMenuEntity));
        Long nowMenuId = menuId;
        // 遍历直到到达父节点
        while (nowMenuId != 0L){
            result.add(userMenusMap.get(nowMenuId).getName());
            nowMenuId = userMenusMap.get(nowMenuId).getParentId();
        }
        return result;
    }

}
